<?php

namespace yunj\library\enum;

abstract class Enum {

    /**
     * @var string
     */
    private $name;

    /**
     * @var mixed
     */
    private $value;

    /**
     * 按枚举类列出的枚举常量名和实例的映射
     * @var array<class-string<Enum>, array<string, Enum>>
     * [
     *      "类名"=>[
     *          "常量名"=>实例
     *      ]
     * ]
     */
    private static $instances = [];

    /**
     * 按枚举类列出的枚举常量名和值的映射
     * @var array<class-string<Enum>, array<string, value>>
     * [
     *      "类名"=>[
     *          "常量名"=>常量值
     *      ]
     * ]
     */
    private static $constants = [];

    /**
     * 获取枚举常量名
     * @return string
     */
    final public function getName(): string {
        return $this->name;
    }

    /**
     * 获取枚举常量值
     * @return mixed
     */
    final public function getValue() {
        return $this->value;
    }

    /**
     * 判断传入常量值的唯一性
     * @param array<name,value> $constants
     * @return bool
     */
    private static function noAmbiguousValues(array $constants): bool {
        foreach ($constants as $value) {
            $names = array_keys($constants, $value, true);
            if (count($names) > 1) return false;
        }
        return true;
    }

    /**
     * 获取当前枚举类的所有可调用常量
     * @return array
     */
    final public static function getConstants(): array {
        if (isset(self::$constants[static::class])) {
            return self::$constants[static::class];
        }

        $reflection = new \ReflectionClass(static::class);
        $constants = [];
        do {
            // 公共常量
            foreach ($reflection->getReflectionConstants() as $reflConstant) {
                if ($reflConstant->isPublic()) $constants[$reflConstant->getName()] = $reflConstant->getValue();
            }
        } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);

        return self::$constants[static::class] = $constants;
    }

    /**
     * 判断是否为枚举值
     * @param $value
     * @param bool $strict 是否严格校验
     * @return bool
     */
    final public static function isValue($value, bool $strict = false): bool {
        return in_array($value, static::getConstants(), $strict);
    }

    /**
     * 通过常量值或枚举实例获取当前枚举实例
     * @param $enumerator
     * @return Enum
     */
    final public static function get($enumerator): Enum {
        if ($enumerator instanceof static) {
            if (get_class($enumerator) !== static::class)
                throw new \InvalidArgumentException(sprintf(
                    '实例类型 %s 与枚举类型 %s 不一致',
                    get_class($enumerator),
                    static::class
                ));
            return $enumerator;
        }
        return static::byValue($enumerator);
    }

    /**
     * 给定常量值获取类实例
     * @param mixed $value
     * @param bool $strict 是否严格模式，严格比对值的类型
     * @return static
     */
    final public static function byValue($value, bool $strict = false): Enum {
        $constants = self::$constants[static::class] ?? static::getConstants();
        $name = array_search($value, $constants, $strict);
        if ($name === false)
            throw new \InvalidArgumentException(sprintf('枚举 %s 值 %s 不存在', static::class,
                is_scalar($value) ? var_export($value, true) : 'of type ' . (is_object($value) ? get_class($value) : gettype($value))
            ));

        return self::$instances[static::class][$name] ?? self::$instances[static::class][$name] = new static($constants[$name], $value);
    }

    /**
     * 给定常量名获取类实例
     * @param string $name 方法名
     * @return static
     */
    final public static function byName(string $name): Enum {
        if (isset(self::$instances[static::class][$name])) {
            return self::$instances[static::class][$name];
        }

        $const = static::class . "::{$name}";
        if (!defined($const)) throw new \InvalidArgumentException("{$const} 未定义");

        // 判断枚举类的每个常量值是否唯一
        if (!isset(self::$instances[static::class]))
            assert(self::noAmbiguousValues(static::getConstants()), static::class . " 常量值存在重复性");

        // 获取常量的值
        $value = constant($const);
        return self::$instances[static::class][$name] = new static($name, $value);
    }

    /**
     * 根据传入的枚举值和其他属性的映射，返回当前枚举属性
     * @param array $map
     * [
     *      "枚举值1"=>属性,
     *      "枚举值2"=>属性,...
     * ]
     * @param mixed|null $map
     * @return mixed|null
     */
    final protected function match(array $map, $default = null) {
        return $map[$this->getValue()] ?? $default;
    }

    /**
     * Enum constructor.
     * @param string $name
     * @param mixed $value
     */
    final private function __construct(string $name, $value) {
        $this->name = $name;
        $this->value = $value;
    }

    /**
     * 获取枚举常量名
     * @return string
     * @see getName()
     */
    public function __toString(): string {
        return $this->getName();
    }

    /**
     * 枚举不可克隆，因为实例是作为单例实现的
     */
    final protected function __clone() {
        throw new \LogicException("枚举不可克隆，因为实例是作为单例实现的");
    }

    /**
     * 枚举不可序列化，因为实例是作为单例实现的
     * 执行serialize()时，先会调用这个函数
     */
    final public function __sleep() {
        throw new \LogicException("枚举不可序列化，因为实例是作为单例实现的");
    }

    /**
     * 枚举不可序列化，因为实例是作为单例实现的
     * 执行unserialize()时，先会调用这个函数
     */
    final public function __wakeup() {
        throw new \LogicException("枚举不可序列化，因为实例是作为单例实现的");
    }

    /**
     * 调用不存在的静态方法时，调用此方法
     * @param string $method 方法名
     * @param array $args 方法参数
     * @return Enum
     */
    final public static function __callStatic(string $method, array $args) {
        return static::byName($method);
    }

    // 以下是帮助方法

    /**
     * 所有枚举的属性值
     * @return array
     */
    public static function allEnumAttrs(): array {
        return [];
    }

    /**
     * 获取当前枚举指定/全部属性配置
     * @param string $key
     * @param null $default
     * @return mixed|null
     */
    public function getAttr(string $key = '', $default = null) {
        $attrs = $this->match(static::allEnumAttrs(), []);
        if (!$key) return $attrs;
        return $attrs[$key] ?? $default;
    }

    /**
     * 获取所有枚举映射的某项属性值
     * @param string $key
     * @return array
     */
    public static function getAllEnumAttrOptions(string $key): array {
        $options = [];
        $allEnumAttrs = static::allEnumAttrs();
        foreach ($allEnumAttrs as $enumVal => $attrs) {
            if (array_key_exists($key, $attrs)) {
                $options[$enumVal] = $attrs[$key];
            }
        }
        return $options;
    }

    /**
     * 获取当前枚举标题
     * @return mixed|null
     */
    public function getTitle() {
        return $this->getAttr('title');
    }

    /**
     * 获取所有枚举值的标题
     * @return array
     */
    public static function getTitleOptions(): array {
        return static::getAllEnumAttrOptions('title');
    }

    /**
     * 获取当前枚举描述
     * @return mixed|null
     */
    public function getDesc() {
        return $this->getAttr('desc');
    }

    /**
     * 获取所有枚举值的描述
     * @return array
     */
    public static function getDescOptions(): array {
        return static::getAllEnumAttrOptions('desc');
    }

    /**
     * 获取枚举值配置属性
     * @return array
     */
    public static function getEnumOptions(): array {
        $textOptions = static::getAllEnumAttrOptions('title');
        if (!$textOptions) {
            $textOptions = static::getAllEnumAttrOptions('desc');
        }
        $colorOptions = static::getAllEnumAttrOptions('color');
        $options = [];
        foreach ($textOptions as $k => $text) {
            $options[$k] = ["text" => $text, "bgColor" => $colorOptions[$k] ?? ''];
        }
        return $options;
    }

}